package com.swak.security.permission;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.springframework.util.CollectionUtils;

import com.swak.cache.LRUCache;
import com.swak.security.Permission;
import com.swak.utils.Lists;
import com.swak.utils.Sets;

public class WildcardPermission implements Permission {

    private LRUCache<Integer, WildcardPermission> cachedPermissions = new LRUCache<>(1000);

    /*--------------------------------------------
    |             C O N S T A N T S             |
    ============================================*/
    protected static final String WILDCARD_TOKEN = "*";
    protected static final String PART_DIVIDER_TOKEN = ":";
    protected static final String SUBPART_DIVIDER_TOKEN = ",";
    protected static final boolean DEFAULT_CASE_SENSITIVE = false;

    /*--------------------------------------------
    |    I N S T A N C E   V A R I A B L E S    |
    ============================================*/
    private List<Set<String>> parts;

    /*--------------------------------------------
    |         C O N S T R U C T O R S           |
    ============================================*/
    protected WildcardPermission() {}

    public WildcardPermission(String wildcardString) {
        this(wildcardString, DEFAULT_CASE_SENSITIVE);
    }

    public WildcardPermission(String wildcardString, boolean caseSensitive) {
      this.parts = this.parseParts(wildcardString, caseSensitive);
    }

    protected  List<Set<String>> parseParts(String wildcardString, boolean caseSensitive) {
        if (wildcardString == null || wildcardString.trim().length() == 0) {
            throw new IllegalArgumentException(
                "Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.");
        }
        
        wildcardString = wildcardString.trim();
        List<String> wildparts = Lists.newArrayList(wildcardString.split(PART_DIVIDER_TOKEN));
        List<Set<String>> parts = new ArrayList<Set<String>>(wildparts.size());
        for (String part : wildparts) {
            Set<String> subparts = Sets.newHashSet(part.split(SUBPART_DIVIDER_TOKEN));
            if (!caseSensitive) {
                subparts = lowercase(subparts);
            }
            if (subparts.isEmpty()) {
                throw new IllegalArgumentException(
                    "Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted.");
            }
            parts.add(subparts);
        }
        if (parts.isEmpty()) {
            throw new IllegalArgumentException(
                "Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted.");
        }
        return parts;
    }

    private Set<String> lowercase(Set<String> subparts) {
        Set<String> lowerCasedSubparts = new LinkedHashSet<String>(subparts.size());
        for (String subpart : subparts) {
            lowerCasedSubparts.add(subpart.toLowerCase());
        }
        return lowerCasedSubparts;
    }

    /*--------------------------------------------
    |  A C C E S S O R S / M O D I F I E R S    |
    ============================================*/
    protected List<Set<String>> getParts() {
        return this.parts;
    }

    /*--------------------------------------------
    |               M E T H O D S               |
    ============================================*/
    @Override
    public boolean implies(Set<String> permissions) {
        if (CollectionUtils.isEmpty(permissions)) {
            return false;
        }

        WildcardPermission wp = this.getWildcardPermission(permissions);
        
        int i = 0;
        for (Set<String> otherPart : wp.getParts()) {
            if (getParts().size() - 1 < i) {
                return true;
            } else {
                Set<String> part = getParts().get(i);
                if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
                    return false;
                }
                i++;
            }
        }

        // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
        for (; i < getParts().size(); i++) {
            Set<String> part = getParts().get(i);
            if (!part.contains(WILDCARD_TOKEN)) {
                return false;
            }
        }

        return true;
    }
    
    /**
     * @param permissions
     * @return
     */
    private WildcardPermission getWildcardPermission(Set<String> permissions) {
        int key = permissions.hashCode();
        if (!this.cachedPermissions.containsKey(key)) {
            List<Set<String>> allparts = Lists.newArrayList();
            for (String permission: permissions) {
                List<Set<String>> parts = this.parseParts(permission, DEFAULT_CASE_SENSITIVE);
                int i = 0;
                for (Set<String> part: parts) {
                    if (allparts.size()<i) {
                        allparts.add(part);
                    } else {
                        allparts.get(i).addAll(part);
                    }
                    i++;
                }
            }
            WildcardPermission wp = new WildcardPermission();
            wp.parts = allparts;
            this.cachedPermissions.put(key, wp);
        }
        return this.cachedPermissions.get(key);
    }

    public String toString() {
        StringBuilder buffer = new StringBuilder();
        for (Set<String> part : parts) {
            if (buffer.length() > 0) {
                buffer.append(":");
            }
            buffer.append(part);
        }
        return buffer.toString();
    }

    public boolean equals(Object o) {
        if (o instanceof WildcardPermission) {
            WildcardPermission wp = (WildcardPermission)o;
            return parts.equals(wp.parts);
        }
        return false;
    }

    public int hashCode() {
        return parts.hashCode();
    }
}
